#include <windows.h>
#include <jni.h>
#include <string>
#include <vector>
#include "resource.h"

using namespace std;

void transfer_params(int, char**, vector<string>&);
string load_string(int);
void load_preset_params(vector<string>&);
void load_preset_vmopts(vector<string>& v);
void setupJavaVMInitArgs(JavaVMInitArgs&, vector<string>&);
void loadJVM(string, string, vector<string>&, vector<string>&);

int main(int argc, char** argv) {
	
	vector<string> params;
	load_preset_params(params);
	transfer_params(argc, argv, params);

	string jni_dll = load_string(IDS_DLL);
	string class_path = load_string(IDS_CLASSPATH);
	string main_class = load_string(IDS_MAINCLASS);

	vector<string> vmopts;
	load_preset_vmopts(vmopts);
	vmopts.push_back("-Djava.class.path=" + class_path);
	// -Djava.library.path=

	loadJVM(jni_dll, main_class, params, vmopts);
}

void severeError(string& text) {
	printf(text.c_str());
}

void transfer_params(int argc, char** argv, vector<string>& v) {
	for(int i = 1; i < argc; i++) {
		v.push_back(string(argv[i]));
	}
}

void tokenize(const string& str,const string& delimiters, vector<string>& tokens)
{   	
	// skip delimiters at beginning.
    	string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    	
	// find first "non-delimiter".
    	string::size_type pos = str.find_first_of(delimiters, lastPos);

    	while (string::npos != pos || string::npos != lastPos)
    	{
        	// found a token, add it to the vector.
        	tokens.push_back(str.substr(lastPos, pos - lastPos));
		
        	// skip delimiters.  Note the "not_of"
        	lastPos = str.find_first_not_of(delimiters, pos);
		
        	// find next "non-delimiter"
        	pos = str.find_first_of(delimiters, lastPos);
    	}
}


string load_string(int id) {
	char buf[1024];
	LoadString(NULL, id, buf, 1024);
	return string(buf);
}

void load_preset_params(vector<string>& v) {
	string params = load_string(IDS_PARAMS);

	string delimiters(" ");
	tokenize(params, delimiters, v);
}

void load_preset_vmopts(vector<string>& v) {
	string params = load_string(IDS_VMOPTS);

	string delimiters(" ");
	tokenize(params, delimiters, v);
}

void setupJavaVMInitArgs(JavaVMInitArgs& jvmInitArgs, vector<string>& params)
{
	try
	{
		memset(&jvmInitArgs, 0, sizeof(jvmInitArgs));
		jvmInitArgs.version  = JNI_VERSION_1_2;


		JavaVMOption* pJvmOptions = new JavaVMOption[params.size()];
		
		int j = 0;
		vector<string>::iterator it;
		for ( it=params.begin() ; it < params.end(); it++ ) {
			pJvmOptions[j].optionString = (char*)(*it).c_str();
			j++;
		}


		jvmInitArgs.nOptions = params.size();
		jvmInitArgs.options  = pJvmOptions;
		jvmInitArgs.ignoreUnrecognized = JNI_FALSE;
	}
	catch(...)
	{
		severeError( string("Error while setting up JVM init arguments.") );
	}
}

void loadJVM(string jni_dll, string main_class, vector<string>& params, vector<string>& vmargs) {
	try
	{
		LoadLibrary((jni_dll + "\\msvcr71.dll").c_str());
		HINSTANCE hJvm = LoadLibrary((jni_dll + "\\server\\jvm.dll").c_str());
		if(hJvm == NULL) {
			throw string("failed to load JVM");
		}

		// setup options
		JavaVMInitArgs jvmInitArgs;
		setupJavaVMInitArgs(jvmInitArgs, vmargs);

		typedef jint (JNICALL CreateJavaVM_t)( JavaVM**, void**, void* );
		CreateJavaVM_t* CreateJavaVM = (CreateJavaVM_t*)GetProcAddress( hJvm, "JNI_CreateJavaVM" );

		JNIEnv* pJniEnvironment;
		JavaVM* pJvm;
		jint result = CreateJavaVM(&pJvm,(void**)&pJniEnvironment,&jvmInitArgs);
		if(pJniEnvironment->ExceptionOccurred() != 0)
		{
			pJniEnvironment->ExceptionDescribe();
			throw string("Error occurred while creating Java VM.");
		}
		if (result < 0)
		{
			throw string("Cannot create Java VM.");
		}		
		
		// find main class
		jclass javaClass = pJniEnvironment->FindClass( main_class.c_str() );
		if(pJniEnvironment->ExceptionOccurred() != 0)
		{
			pJniEnvironment->ExceptionDescribe();
			throw string("Error occurred while attempting to find Java class " + main_class);
		}
		if (javaClass == 0)
		{
			throw string( "Cannot find main Java class " + (main_class) );
		}

		jmethodID javaMethodId = pJniEnvironment->GetStaticMethodID(javaClass, "main", "([Ljava/lang/String;)V");
		if(pJniEnvironment->ExceptionOccurred() != 0)
		{
			pJniEnvironment->ExceptionDescribe();
			throw string("Error occurred while getting main method in Java class " + main_class);
		}
		if (javaMethodId == 0)
		{
			throw string("Cannot find main method in Java class.");
		}

		jobjectArray argsJObjArray = (jobjectArray)pJniEnvironment->NewObjectArray( params.size(),
							pJniEnvironment->FindClass("java/lang/String"),
							pJniEnvironment->NewStringUTF(""));
		if (argsJObjArray == 0)
		{
			throw string("Error creating new object array to hold command line arguments.");
		}

		for(unsigned int i=0; i < params.size(); i++)
		{
			pJniEnvironment->SetObjectArrayElement(argsJObjArray,i,pJniEnvironment->NewStringUTF(params.at(i).c_str()));
		}
		if(pJniEnvironment->ExceptionOccurred() != 0)
		{
			pJniEnvironment->ExceptionDescribe();
			throw string("Error occurred while setting command line arguments for the Java class " + main_class);
		}

		// call main method in Java class passing arguments
		pJniEnvironment->CallStaticVoidMethod(javaClass, javaMethodId, argsJObjArray);
		if(pJniEnvironment->ExceptionOccurred() != 0)
		{
			pJniEnvironment->ExceptionDescribe();
			throw string("Error occured while calling main method in Java class " + main_class);
		}

		pJvm->DestroyJavaVM();
		::FreeLibrary(hJvm);
	
	}
	catch(string& se)
	{
		severeError( se );
	}
	catch(...)
	{
		severeError( string("Error while launching program with JVM." ));
	}
}